package pub.wkq.gateway.filter;

import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.server.ServerWebExchange;
import pub.wkq.common.base.BaseInfoProperties;
import pub.wkq.common.result.JSONResult;
import pub.wkq.common.result.ResponseStatusEnum;
import pub.wkq.common.utils.JWTUtils;
import pub.wkq.common.utils.SerializationUtils;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;


/**
 * <h1>安全JWT过滤器</h1>
 *
 * @author 王开琦
 * @since 2023-06-12
 * 全局路由拦截使用 GlobalFilter
 * 特定路由拦截使用 GatewayFilter
 */
@Component
@Slf4j
public class SecurityFilterJWT extends BaseInfoProperties implements GlobalFilter, Ordered {
    @Resource
    private ExcludeUrlProperties excludeUrlProperties;
    @Resource
    private JWTUtils jwtUtils;
    /**
     * 路径匹配器
     */
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    private final String headerUserToken = "headerUserToken";

    /**
     * @param exchange 获取和请求响应相关的内容
     * @param chain    过滤器链
     * @return Mono<Void> 代表过滤器链的执行结果
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求的uri
        String path = exchange.getRequest().getURI().getPath();
        log.info("当前请求的uri：{}", path);
        List<String> excludeList = excludeUrlProperties.getUrls();
        log.info("白名单：{}", excludeList);
        //判断是否在白名单中，如果是则放行，不是则进行 JWT 验证。注意要校验白名单 null，使用集合是否存在某个元素的方法
        if (excludeList != null && !excludeList.isEmpty()) {
            //遍历白名单 url
            for (String excludeUrl : excludeList) {
                //判断当前请求的 url 是否以白名单中的 url 开头
                if (antPathMatcher.match(excludeUrl, path)) {
                    log.info("当前请求的uri：{}，在白名单中，放行", path);
                    //是，放行
                    return chain.filter(exchange);
                }
            }
        }
        //获取请求头中的 authorization 字段
        String userToken = exchange.getRequest().getHeaders().getFirst(headerUserToken);
        if (StringUtils.isNotBlank(userToken)) {
            String[] tokenArr = userToken.split(JWTUtils.at);
            if (tokenArr.length != 2) {
                return renderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN);
            }
            //获取 token 的令牌和前缀
            String token = tokenArr[1];
            String tokenPrefix = tokenArr[0];
            switch (tokenPrefix) {
                case TOKEN_USER_PREFIX:
                    return dealJWT(token, exchange, chain, APP_USER_JSON);
                case TOKEN_SAAS_PREFIX:
                    return dealJWT(token, exchange, chain, SAAS_USER_JSON);
                case TOKEN_ADMIN_PREFIX:
                    return dealJWT(token, exchange, chain, ADMIN_USER_JSON);
            }
        }
        return renderErrorMsg(exchange, ResponseStatusEnum.UN_LOGIN);
    }

    /**
     * @param token
     * @param exchange
     * @param chain
     * @param key
     * @return
     */
    private Mono<Void> dealJWT(String token, ServerWebExchange exchange, GatewayFilterChain chain, String key) {
        try {
            String userJson = jwtUtils.checkJwt(token);
            ServerWebExchange serverWebExchange = setNewHeader(exchange, key, userJson);
            return chain.filter(serverWebExchange);
        } catch (ExpiredJwtException e) {
            //token 过期
            log.error("token 过期");
            return renderErrorMsg(exchange, ResponseStatusEnum.JWT_EXPIRE_ERROR);
        } catch (Exception e) {
            return renderErrorMsg(exchange, ResponseStatusEnum.JWT_SIGNATURE_ERROR);
        }
    }

    /**
     * 重新包装并且返回错误信息
     *
     * @param exchange           用于获取 response
     * @param responseStatusEnum 错误信息
     * @return Mono<Void>
     */
    public Mono<Void> renderErrorMsg(ServerWebExchange exchange, ResponseStatusEnum responseStatusEnum) {
        //1. 获得 response
        ServerHttpResponse response = exchange.getResponse();
        //2. 构建 jsonResult
        JSONResult jsonResult = JSONResult.exception(responseStatusEnum);
        //3. 修改  response 的code 为 401
        response.setStatusCode(HttpStatus.BAD_GATEWAY);
        if (!response.getHeaders().containsKey("Content-Type"))
            response.getHeaders().add("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE);

        // 5. 转换json并且向response中写入数据
        String resultJson = SerializationUtils.serializeObject(jsonResult);
        DataBuffer dataBuffer = response.bufferFactory().wrap(resultJson.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(dataBuffer));
    }

    /**
     * <h2>重新构建新的 request</h2>
     *
     * @param exchange    用于获取 request
     * @param headerKey   请求头的 key
     * @param headerValue 请求头的 value
     * @return ServerWebExchange
     */
    public ServerWebExchange setNewHeader(ServerWebExchange exchange, String headerKey, String headerValue) {
        //重新构建新的 request
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()  //修改
                .header(headerKey, headerValue).build();
        //替换原来的 request
        return exchange.mutate().request(serverHttpRequest).build();
    }

    /**
     * 过滤器的顺序，数字越小，优先级越高
     *
     * @return 顺序
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
